#!/usr/bin/python
# -*- coding: utf-8 -*-
# pglvmbackup.py
#
#########################################################################################
#
# $Id$
# $Author$
# $Date$
# $LastChangedBy$
# $LastChangedDate$
# $Revision$
#
#########################################################################################

# imports

import psycopg2
import sys
import os
import time
from time import *
from optparse import OptionParser 
import gc
import logging
import platform
import ConfigParser

# operational time

t1 = clock()

# enable gc

gc.enable()

# OptionParser

parser = OptionParser("pglvmbackup.py [options]")

# Options

parser.add_option("-a", "--inifile", dest="inifile", help="inifile")
parser.add_option("-u", "--dbuser", dest="dbuser", help="database user")
parser.add_option("-p", "--dbpassword", dest="dbpassword", help="database password")
parser.add_option("-c", "--compression", dest="compression", default="gzip", help="compression tool: gzip, pigz; default: gzip")
parser.add_option("-v", "--volumegroup", dest="volumegroup", help="volume group name")
parser.add_option("-l", "--logicalvolume", dest="logicalvolume", help="logical volume name")
parser.add_option("-m", "--mountpoint", dest="mountpoint", help="mointpoint for temporary snapshot")
parser.add_option("-b", "--mountopts", dest="mountopts", default="defaults", help="moint options e.g. nouuid")
parser.add_option("-s", "--size", dest="size", help="snapshot size")
parser.add_option("-n", "--name", dest="name", help="name of temporary snapshot LV")
parser.add_option("-f", "--filesystem", dest="filesystem", help="filesystem of logical volume")
parser.add_option("-t", "--target", dest="target", help="target (directory+filename) for backup file")
parser.add_option("-d", "--debug", dest="debug", default="false", help="useful for debugging; default: false")
parser.add_option("-o", "--port", dest="port", default="5432", help="database port; default: 5432")
parser.add_option("-i", "--pidfile", dest="pidfile", default="/var/run/pglvmbackup.pid", help="pidfile, default: /var/run/pglvmbackup.pid")

# parse args

(options, args) = parser.parse_args()

# read configuration file

try:
    config = ConfigParser.ConfigParser()
    config.read(options.inifile)
    options.pidfile=config.get('pglvmbackup','pidfile')
    options.dbuser=config.get('pglvmbackup','dbuser')
    options.dbpassword=config.get('pglvmbackup','dbpassword')
    options.compression=config.get('pglvmbackup','compression')
    options.volumegroup=config.get('pglvmbackup','volumegroup')
    options.logicalvolume=config.get('pglvmbackup','logicalvolume')
    options.mountpoint=config.get('pglvmbackup','mountpoint')
    options.mountopts=config.get('pglvmbackup','mountopts')
    options.size=config.get('pglvmbackup','size')
    options.name=config.get('pglvmbackup','name')
    options.filesystem=config.get('pglvmbackup','filesystem')
    options.target=config.get('pglvmbackup','target')
    options.debug=config.get('pglvmbackup','debug')

except Exception,e:
    gc.collect()

# pid file

pid = str(os.getpid())
pidfile = str(options.pidfile)
if os.path.isfile(pidfile):
    print "%s already exists, exiting" % pidfile
    sys.exit(1)
else:
    file(pidfile, 'w').write(pid)

# functions
def unlink_pidfile(pidfile):
    # unlink pidfile
    log.debug("unlink pidfile")
    os.unlink(pidfile)
    return

# define logging

log = logging.getLogger("pglvmbackup")
out_hdlr = logging.StreamHandler(sys.stdout)
out_hdlr.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
if options.debug == 'true':
    out_hdlr.setLevel(logging.DEBUG)
else:
    out_hdlr.setLevel(logging.ERROR)
log.addHandler(out_hdlr)
log.setLevel(logging.DEBUG)

log.debug("starting pglvmbackup")

# log all options for debugging

log.debug(options)

# log some system information

log.debug(sys.platform)
log.debug(os.getuid())
log.debug(os.getpid())
log.debug(os.getcwd())
log.debug(os.uname())
log.debug(os.times())
log.debug(os.name)
log.debug(os.environ)
log.debug(platform.machine())
log.debug(platform.version())
log.debug(platform.platform())
log.debug(platform.uname())
log.debug(platform.system())
log.debug(platform.processor())
log.debug("pid: "+str(pid))
log.debug("pidfile: "+str(pidfile))

log.debug("check arguments")

# check arguments

if not options.dbuser:
    log.debug("ERROR: option dbuser not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.dbpassword:
    log.debug("ERROR: option dbpassword not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.volumegroup:
    log.debug("ERROR: option volumegroup not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.logicalvolume:
    log.debug("ERROR: option logicalvolume not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.mountpoint:
    log.debug("ERROR: option mountpoint not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.size:
    log.debug("ERROR: option size not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.name:
    log.debug("ERROR: option name not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.filesystem:
    log.debug("ERROR: option filesystem not given")
    unlink_pidfile(pidfile)
    exit(1)
if not options.target:
    log.debug("ERROR: option target not given")
    unlink_pidfile(pidfile)
    exit(1)

log.debug("check binaries")

# check binaries

binaries = ['/bin/gzip','/bin/mount','/bin/umount','/sbin/lvcreate','/sbin/lvremove','/bin/tar']

for i in binaries:
    if not os.path.isfile(i):
        log.debug("ERROR: "+i+" doesn't exist")
        unlink_pidfile(pidfile)
        exit(1)

del i
del binaries
gc.collect()

log.debug("check if PostgreSQL WAL is active")

# check if PostgreSQL WAL is active, else error

log.debug("establishing PostgreSQL connection")

# establish PostgreSQL connection

try:
    conn = psycopg2.connect("dbname='postgres' user='"+options.dbuser+"' host='localhost' password='"+options.dbpassword+"' port="+options.port+" ")
    log.debug("PostgreSQL connection established")
except Exception,e:
    log.debug("PostgreSQL connection failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)

log.debug("define cursor")

# define cursor

cur = conn.cursor()

log.debug("check database grants")

# check database grants

cur.execute("SELECT usesuper FROM pg_user WHERE usename='"+options.dbuser+"';")
row = cur.fetchone()
if not str(row[0]).lower == str("True").lower:
    log.debug("no SUPERUSER grant given. Bye bye.")
    unlink_pidfile(pidfile)
    exit(1)
del row
gc.collect

log.debug("start pg_backup")

# start pg_backup
try:
    cur.execute("""SELECT pg_start_backup('pglvmbackup', true);""")
    rows = cur.fetchall()
    for row in rows:
        log.debug(row[0])
except Exception,e:
    log.debug("SELECT pg_start_backup() failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del rows
del row
gc.collect
log.debug("get PostgreSQL information")

# get PostgreSQL information

try:
    cur.execute("""SELECT version();""")
    rows = cur.fetchall()
    for row in rows:
        log.debug(row[0])
except Exception,e:
    log.debug("SELECT version() failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del rows
del row
gc.collect
log.debug("create lvm snapshot")

# create lvm snapshot

try:
    foo = os.popen("lvcreate -s "+options.logicalvolume+" --name "+options.name+" --size "+options.size+" ")
    log.debug(foo)
except Exception,e:
    log.debug("lvcreate failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del foo
gc.collect()

log.debug("stop pg_backup")

# stop pg_backup

try:
    cur.execute("""SELECT pg_stop_backup();""")
    rows = cur.fetchall()
    for row in rows:
        log.debug(row[0])
except Exception,e:
    log.debug("SELECT pg_stop_backup() failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del rows
del row
gc.collect()

log.debug("mount snapshot")

# mount snapshot

try:
    foo = os.popen("mount --verbose -o "+str(options.mountopts)+" -t "+options.filesystem+" /dev/"+options.volumegroup+"/"+options.name+" "+options.mountpoint).read()
    log.debug(foo)
except Exception,e:
    log.debug("mount snapshot failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del foo
gc.collect()

log.debug("zip it")

# tar+zip it

try:
    foo = os.popen("tar --verbose --use-compress-program="+options.compression+" -Ppcvf "+options.target+" --directory="+options.mountpoint+" .").read()
    log.debug(foo)
except Exception,e:
    log.debug("tar failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del foo
gc.collect()

log.debug("umount snapshot")
# umount snapshot

try:
    foo = os.popen("umount --verbose "+options.mountpoint).read()
    log.debug(foo)
except Exception,e:
    log.debug("umount failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del foo
gc.collect()

log.debug("remove lvm snapshot")

# remove lvm snapshot

try:
    foo = os.popen("lvremove --force /dev/"+options.volumegroup+"/"+options.name).read()
    log.debug(foo)
except Exception,e:
    log.debug("lvremove failed")
    log.debug("Error: "+str(e))
    unlink_pidfile(pidfile)
    exit(1)
del foo
gc.collect()

t2 = clock()
dt = t2 - t1
log.debug("time spent: "+str(dt))

del t1
del t2
del dt
gc.collect()

# genereate restore script

cur.execute("SELECT setting FROM pg_settings WHERE LOWER(name) = LOWER('data_directory');")
row = cur.fetchone()
pdatadir=str(row[0])
log.debug("PostgreSQL datadir: "+pdatadir)

del row
gc.collect


log.debug("generate restore script")
log.debug("use this for restore:")
log.debug("-----------------------------------------")
log.debug("#!/bin/bash")
log.debug("/etc/init.d/postgresql stop")
log.debug("cd "+pdatadir)
log.debug("rm -fr *")
log.debug("tar xzf "+options.target)
log.debug("/etc/init.d/postgresql start")
log.debug("-----------------------------------------")
log.debug("closing database connection")

# close database connection

conn.close()
del conn
gc.collect()

unlink_pidfile(pidfile)

log.debug("finished")
# finished
sys.exit(0)
